module AboutYou
  module SDK
    module Criteria
      ###
      # This class is used as the criteria for a product search
      # Its instance is meant to be passed to the fetchProducrSearch method
      # To add a specific criteria simply call the method on the instance
      #
      # author:: Collins GmbH & Co KG
      ###
      class ProductSearchCriteria
        # api-call-name for sorting after relevance
        SORT_TYPE_RELEVANCE   = 'relevance'
        # api-call-name for sorting after updated_date
        SORT_TYPE_UPDATED     = 'updated_date'
        # api-call-name for sorting after created_date
        SORT_TYPE_CREATED     = 'created_date'
        # api-call-name for sorting after most_viewed
        SORT_TYPE_MOST_VIEWED = 'most_viewed'
        # api-call-name for sorting after price
        SORT_TYPE_PRICE       = 'price'
        # Sort-argument passed to api for relevance
        SORT_RELEVANCE = 'relevance'
        # Sort-argument passed to api for last updated
        SORT_UPDATED = 'updated_date'
        # Sort-argument passed to api for creation date
        SORT_CREATED = 'created_date'
        # Sort-argument passed to api for most viewed
        SORT_MOST_VIEWED = 'most_viewed'
        # Sort-argument passed to api for price
        SORT_PRICE = 'price'
        # api-call-name for sorting ascending
        SORT_ASC  = 'asc'
        # api-call-name for sorting descending
        SORT_DESC = 'desc'

        # api-call-name for filtering the sale
        FILTER_SALE          = 'sale'
        # api-call-name for filtering the categories
        FILTER_CATEGORY_IDS  = 'categories'
        # api-call-name for filtering the prices
        FILTER_PRICE         = 'prices'
        # api-call-name for filtering the searchword
        FILTER_SEARCHWORD    = 'searchword'
        # api-call-name for filtering the facets
        FILTER_ATTRIBUTES    = 'facets'

        # the filter applied to the api-call
        attr_accessor :filter
        # a specification of the result which should be fetched from api
        attr_accessor :result
        # the session id of a user
        attr_accessor :session_id

        ###
        # Constructor for AboutYou::SDK::Criteria::ProductSearchCriteria
        #
        # * *Args*    :
        #   - +session_id+ -> the session is of an user
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def initialize(session_id)
          self.filter    = {}
          self.session_id = session_id
          self.result    = {}
        end

        ###
        # adds the filter passed to the api the given filter value
        #
        # * *Args*    :
        #   - +key+ -> the name of the filter which should be applied
        #   - +value+ -> the value of the filter which should be applied
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by(key, value)
          @filter[key] = value

          self
        end

        ###
        # gets the filter value of a given filter key
        #
        # * *Args*    :
        #   - +key+ -> the name of the filter which value should be returned
        #
        # * *Returns* :
        #   - either a value of a filter or nil if filter key not set
        ###
        def filter(key)
          @filter[key] if @filter[key]
        end

        ###
        # sets a filter for filtering by sale
        #
        # * *Args*    :
        #   - +sale+ -> determines whether to filter by sale or not [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_sale(sale = true)
          sale = nil unless sale.is_a?(TrueClass) || sale.is_a?(FalseClass)

          filter_by(FILTER_SALE, sale)
        end

        ###
        # sets a filter for filtering by a searchword
        #
        # * *Args*    :
        #   - +searchword+ -> the searchword which should be used for filtering
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_searchword(searchword)
          filter_by(FILTER_SEARCHWORD, searchword)
        end

        ###
        # sets a filter for filtering by category ids
        #
        # * *Args*    :
        #   - +categoryIds+ -> the category ids used for filtering
        #   - +append+ -> determines whether to append the category ids to an already set value or not [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_category_ids(category_ids, append = false)
          category_ids = filter(FILTER_CATEGORY_IDS) + category_ids if
          append && filter(FILTER_CATEGORY_IDS)

          category_ids = category_ids.uniq

          filter_by(FILTER_CATEGORY_IDS, category_ids)
        end

        ###
        # sets a filter for filtering by category ids
        #
        # * *Args*    :
        #   - +attributes+ -> Array of Hashes containing the relation Group_Id => Array of Facet_Ids
        #   - +append+ -> determines whether to append the category ids to an already set value or not [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_facet_ids(attributes, append = false)
          merged = attributes[0]
          if append && filter(FILTER_ATTRIBUTES)
            merged = filter(FILTER_ATTRIBUTES)
            attributes.each do |group_id, facet_ids|
              if merged.key?(group_id)
                merged[group_id] = (merged[group_id] + facet_ids).uniq
              else
                merged[group_id] = facet_ids
              end
            end
          end

          filter_by(FILTER_ATTRIBUTES, merged)
        end

        ###
        # sets a filter for filtering by Facet-Group
        #
        # * *Args*    :
        #   - +facet_group+ -> Instance of AboutYou::SDK::Model::FacetGroup
        #   - +append+ -> determines whether to append the category ids to an already set value or not [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_facet_group(facet_group, append = false)
          filter_by_facet_ids(facet_group.ids, append)
        end

        ###
        # sets a filter for filtering by Facet-Groupset
        #
        # * *Args*    :
        #   - +facet_group_set+ -> Instance of AboutYou::SDK::Model::FacetGroupSet
        #   - +append+ -> determines whether to append the category ids to an already set value or not [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_facet_group_set(facet_group_set, append = false)
          filter_by_facet_ids(facet_group_set.ids, append)
        end

        ###
        # sets a filter for filtering by Price range
        #
        # * *Args*    :
        #   - +from+ -> states the starting value of the price filter
        #   - +to+ -> states the end value of the price filter
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def filter_by_price_range(from = 0, to = 0)
          from = Integer(from)
          to = Integer(to)
          price = {}

          price['from'] = from if from > 0
          price['to'] = to if to > 0

          filter_by(FILTER_PRICE, price)
        end

        ###
        # sets a sorting-filter and direction
        #
        # * *Args*    :
        #   - +type+ -> states the type of the sorting
        #   - +direction+ -> the direction of the sorting [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def sort_by(type, direction = SORT_ASC)
          result['sort'] = {
            'by'        => type,
            'direction' => direction
          }

          self
        end

        ###
        # sets a limit and offset for the product search
        #
        # * *Args*    :
        #   - +limit+ -> limits the amount of results received from the api
        #   - +offset+ -> sets an offset to skip results [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def set_limit(limit, offset = 0)
          limit = [[limit, 200].min, 0].max
          result['limit'] = limit

          offset = [offset, 0].max
          result['offset'] = offset

          self
        end

        ###
        # this method determines whether you receive only sales products or not
        #
        # * *Args*    :
        #   - +enable+ -> if true api will return only sales products [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def select_sales(enable = true)
          if enable
            result['sale'] = true
          else
            result['sale'] = nil
          end

          self
        end

        ###
        # this method determines whether you receive price ranges or not
        #
        # * *Args*    :
        #   - +enable+ -> if true api will return price ranges [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def select_price_ranges(enable = true)
          if enable
            result['price'] = true
          else
            result['price'] = nil
          end

          self
        end

        ###
        # this method adds a field to the api-request which enables you to
        # receive Facets by a given group id
        #
        # * *Args*    :
        #   - +group_id+ -> group id which facets you want to receive
        #   - +limit+ -> limits the received facets
        #
        # * *Fails* :
        #   - if group_id is not convertable in an Integer
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def select_facets_by_group_id(group_id, limit)
          check_facet_limit(limit)

          fail "InvalidArgumentException! Group id must be an integer or a
          string containing digits" unless
          group_id.is_a?(Fixnum) || Integer(group_id)

          result['facets'] = {} unless result['facets']

          result['facets'][String(group_id)] = {
            'limit' => limit
          } unless
          result['facets'][group_id]

          self
        end

        ###
        # this method will make the api return all facets
        #
        # * *Args*    :
        #   - +limit+ -> limits the received facets
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def select_all_facets(limit)
          check_facet_limit(limit)
          result['facets'] = {
            FACETS_ALL => {
              'limit' => limit
            }
          }

          self
        end

        ###
        # this method checks whether a given facet limit is valid or not
        #
        # * *Args*    :
        #   - +limit+ -> limit which should be checked
        #
        # * *Fails* :
        #   - if limit is not an integer
        #   - if limit is smaller then -1
        #
        # * *Returns* :
        #   - nil if there is no error
        ###
        def check_facet_limit(limit)
          fail 'InvalidArgumentException! limit must be an integer' unless
          limit.is_a?(Fixnum) || limit.is_a?(Integer)

          fail 'InvalidArgumentException! limit must be positive or -1
            for unlimited facets' if limit < -1

          nil
        end

        ###
        # this method lets the api return the categories if stated so
        #
        # * *Args*    :
        #   - +enable+ -> determines whether or not the categories should be received from the api [optional]
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def select_categories(enable = true)
          if enable
            result['categories'] = true
          else
            result['categories'] = nil
          end

          self
        end

        ###
        # this method lets the api return products. It will prefer to return
        # products which are given by +ids+
        #
        # * *Args*    :
        #   - +ids+ -> the products which should be boosted or the ids of them
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def boost_products(ids)
          mapped_ids = []
          ids = ids.map do
            |val|
            mapped_ids.push(val.id) if val.instance_of?(Product)
            mapped_ids.push(val)
          end
          mapped_ids = mapped_ids.uniq
          result['boosts'] = ids

          self
        end

        ###
        # this method lets you select certain
        # AboutYou::SDK::Criteria::ProductFields which will be returned from api
        #
        # * *Args*    :
        #   - +fields+ -> an Array of constants from AboutYou::SDK::Criteria::ProductFields
        #
        # * *Returns* :
        #   - an instance of AboutYou::SDK::Criteria::ProductSearchCriteria
        ###
        def select_product_fields(fields)
          result['fields'] =
            AboutYou::SDK::Criteria::ProductFields.filter_fields(fields)

          self
        end

        ###
        # this method decides whether categories are required to fetch from api
        #
        # * *Returns* :
        #   - a boolean stating whether to fetch categories from api or not
        ###
        def requires_categories
          (result.key?('fields') &&
            AboutYou::SDK::Criteria::ProductFields.requires_categories(
            result['fields']
          )) || (
            result.key?('categories') &&
              result['categories']
            )
        end

        ###
        # this method decides whether facets are required to fetch from api
        #
        # * *Returns* :
        #   - a boolean stating whether to fetch facets from api or not
        ###
        def requires_facets
          (result.key?('fields') &&
            AboutYou::SDK::Criteria::ProductFields.requires_facets(
            result['fields']
          )) || (
            result.key?('facets') &&
              !result['facets'].empty?
            )
        end

        ###
        # this method turns an instance of
        # AboutYou::SDK::Criteria::product_search_criteria in an Array which can
        # be passed directly to the api
        #
        # * *Returns* :
        #   - an Array containing all the parameters set on the instance
        ###
        def to_array
          params = {
            'session_id' => session_id
          }

          params['result'] = result if result
          params['filter'] = @filter unless @filter.empty?

          params
        end
      end
    end
  end
end
